Classifying keys in a piece of music using a Hidden Markov Model trained using Spotify data.
Let's visualize the Chroma vectors.
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML, Audio
import pandas as pd
import IPython
df = pd.read_csv('../example/39C5FuZ8C8M0QI8CrMsPkR.txt')
df
| start | duration | confidence | pitch | chroma | |
|---|---|---|---|---|---|
| 0 | 0.00000 | 0.11279 | 0.000 | 1.000 | C (also B♯, D#) |
| 1 | 0.00000 | 0.11279 | 0.000 | 0.707 | C♯, D♭ (also B#) |
| 2 | 0.00000 | 0.11279 | 0.000 | 0.374 | D (also C#, E#) |
| 3 | 0.00000 | 0.11279 | 0.000 | 0.391 | D♯, E♭ (also F#) |
| 4 | 0.00000 | 0.11279 | 0.000 | 0.390 | E (also D#, F♭) |
| ... | ... | ... | ... | ... | ... |
| 18199 | 466.14078 | 1.49923 | 0.382 | 0.672 | G (also F#, A#) |
| 18200 | 466.14078 | 1.49923 | 0.382 | 0.563 | G♯, A♭ |
| 18201 | 466.14078 | 1.49923 | 0.382 | 0.490 | A (also G#, B#) |
| 18202 | 466.14078 | 1.49923 | 0.382 | 0.561 | A♯, B♭ (also C#) |
| 18203 | 466.14078 | 1.49923 | 0.382 | 0.445 | B (also A#, C♭) |
18204 rows × 5 columns
max_pitch = df.groupby('start')['pitch'].transform(max) == df['pitch']
linedf = df[max_pitch]
linedf
| start | duration | confidence | pitch | chroma | |
|---|---|---|---|---|---|
| 0 | 0.00000 | 0.11279 | 0.000 | 1.0 | C (also B♯, D#) |
| 13 | 0.11279 | 0.22390 | 1.000 | 1.0 | C♯, D♭ (also B#) |
| 25 | 0.33669 | 0.13324 | 0.459 | 1.0 | C♯, D♭ (also B#) |
| 39 | 0.46993 | 0.20295 | 0.484 | 1.0 | D♯, E♭ (also F#) |
| 55 | 0.67288 | 0.24562 | 0.729 | 1.0 | G (also F#, A#) |
| ... | ... | ... | ... | ... | ... |
| 18154 | 464.90414 | 0.19773 | 0.176 | 1.0 | A♯, B♭ (also C#) |
| 18156 | 465.10187 | 0.53959 | 0.479 | 1.0 | C (also B♯, D#) |
| 18173 | 465.64145 | 0.23755 | 0.373 | 1.0 | F (also E♯, G#) |
| 18185 | 465.87900 | 0.26177 | 0.114 | 1.0 | F (also E♯, G#) |
| 18194 | 466.14078 | 1.49923 | 0.382 | 1.0 | D (also C#, E#) |
1520 rows × 5 columns
def create_frame(frame, ax): # arguments are `frame number` and `fargs`
ax.cla()
window = 5 # show a window of 5 seconds
step = frame/20 # advance 0.05 seconds per frame
x1 = -window/2 + step
x2 = window/2 + step
scattersamples = (df['start'] >= x1) & (df['start'] <= x2)
sns.scatterplot(x='start', y='chroma', size='pitch',
data=df[scattersamples], ax=ax, hue='chroma', legend=False)
linesamples = (linedf['start'] >= x1) & (linedf['start'] <= x2)
sns.lineplot(x='start', y='chroma', alpha=0.5,
data=linedf[linesamples], ax=ax, sort=False, legend=False)
ax.set_xlim(x1, x2)
line1, = ax.plot(
[x1+window/2, x1+window/2],
['C (also B♯, D#)', 'B (also A#, C♭)'],
'--', linewidth=2, label='Dashes set retroactively')
plt.xlabel('time (in seconds)')
plt.title('Chroma vectors of first 5 seconds of \'Foreplay / Long Time\' track.')
fig, ax = plt.subplots(figsize=(10, 5))
create_frame(0, ax)
fig, ax = plt.subplots(figsize=(10, 5))
animation = FuncAnimation(fig, create_frame, frames=300, fargs=(ax,),
interval=50) # Interval at 1000/50 = 20 frames per second
HTML(animation.to_jshtml())